contents
자바에서 Comparator 는 특정 클래스의 객체에 대한 사용자 정의 순서를 지정하는 데 사용되는 강력한 함수형 인터페이스입니다. 이는 클래스 자체를 수정할 수 없거나 여러 가지 다른 정렬 규칙이 필요할 때, 정렬 로직을 외부에서 별도로 제어할 수 있는 필수적인 방법을 제공합니다.
Collections.sort()나 List.sort()와 같은 정렬 메서드에 두 객체를 어떻게 비교해야 하는지 정확히 알려주는 맞춤형 지침서를 제공하는 것으로 생각할 수 있습니다.
Comparator가 해결하는 문제
어떤 객체들은 이미 스스로 정렬하는 방법을 알고 있는데 왜 Comparator가 필요할까요? Comparator는 주로 세 가지 시나리오에서 사용됩니다.
- 서드파티 클래스 정렬: 소유하고 있지 않은 클래스(예: 라이브러리에서 가져온 클래스)의 객체를 정렬하고 싶을 때. 이 클래스를 수정하여 정렬 로직을 추가할 수 없으므로, 로직을 외부에서 제공해야 합니다.
- 다중 정렬 순서:
Person클래스가 있고, 사람 목록을 때로는 이름의 알파벳순으로, 때로는 나이순으로, 때로는 도시순으로 다르게 정렬해야 할 때.Comparator를 사용하면 각 경우에 대한 별도의 정렬 전략을 만들 수 있습니다. - "자연스러운" 순서가 없는 클래스 정렬: 일부 클래스는 명확한 "자연스러운" 순서가 없습니다.
Comparator를 사용하면 특정 상황에 맞는 의미 있는 순서를 정의할 수 있습니다.
Comparator vs. Comparable ⚖️
이것은 이해해야 할 가장 중요한 차이점입니다. 두 인터페이스 모두 정렬을 위한 것이지만, 목적이 다릅니다.
| 특징 | Comparable |
Comparator |
|---|---|---|
| 목적 | 클래스의 자연스러운, 기본 순서를 정의. | 사용자 정의, 외부 정렬 로직을 정의. 한 클래스에 대해 여러 개의 다른 비교자를 가질 수 있음. |
| 구현 | 클래스 자체가 이 인터페이스를 구현해야 함. | 별도의 클래스가 이 인터페이스를 구현함. |
| 메서드 | public int compareTo(T other) |
public int compare(T o1, T o2) |
| 패키지 | java.lang |
java.util |
| 비유 | Person 객체는 자신의 나이를 알고 있음. 이것이 "자연스러운" 비교. |
외부의 "판사"는 사람들을 키순으로, 그 다음엔 이름순으로 줄을 세울 수 있음. 판사가 비교 로직을 제공. |
요컨대, 클래스를 정렬하는 가장 명백한 단 하나의 방법에는 Comparable을 사용하고, 그 외 모든 경우에는 Comparator를 사용하세요.
Comparator 구현 방법
Comparator를 만드는 방법에는 고전적인 접근 방식부터 자바 8에서 도입된 현대적이고 간결한 방법까지 여러 가지가 있습니다.
예제를 위해 간단한 Player 클래스를 사용하겠습니다.
class Player {
private String name;
private int score;
// ... 생성자, getter, toString ...
}
1. 고전적인 방식: 별도의 클래스
Comparator 인터페이스를 구현하는 독립적인 클래스를 만들 수 있습니다.
class SortByScore implements Comparator {
@Override
public int compare(Player p1, Player p2) {
// 오름차순 정렬
return Integer.compare(p1.getScore(), p2.getScore());
}
}
// 사용법:
// players.sort(new SortByScore());
2. 현대적인 방식: 람다 표현식 (자바 8부터)
Comparator는 함수형 인터페이스이므로(추상 메서드가 compare 하나뿐임), 람다 표현식을 사용할 수 있습니다. 훨씬 더 간결합니다.
// 점수 오름차순 정렬
Comparator sortByScore = (p1, p2) -> Integer.compare(p1.getScore(), p2.getScore());
// 이름 알파벳순 정렬
Comparator sortByName = (p1, p2) -> p1.getName().compareTo(p2.getName());
// 사용법:
// players.sort(sortByScore);
3. 최고의 방식: Comparator 정적 헬퍼 메서드 (자바 8부터)
자바 8은 강력한 정적 헬퍼 메서드를 추가하여 Comparator를 훨씬 더 쉽게 사용할 수 있게 만들었습니다. 이것이 가장 가독성이 좋고 권장되는 접근 방식입니다.
Comparator.comparing(keyExtractor): 객체에서 키를 추출하여 비교하는 비교자를 만듭니다.
// 이름으로 정렬
Comparator sortByName = Comparator.comparing(Player::getName);
// 점수로 정렬
Comparator sortByScore = Comparator.comparingInt(Player::getScore); // int에 최적화됨
// 사용법:
// players.sort(Comparator.comparing(Player::getName));
thenComparing()으로 다중 레벨 정렬: 비교자를 쉽게 연결하여 다중 레벨 정렬을 만들 수 있습니다. 예를 들어, 점수로 정렬하고 점수가 같으면 이름으로 정렬합니다.
Comparator sortByScoreThenName = Comparator
.comparingInt(Player::getScore)
.thenComparing(Player::getName);
// 사용법:
// players.sort(sortByScoreThenName);
reversed()로 내림차순 정렬: 어떤 비교자의 순서든 뒤집을 수 있습니다.
// 점수 내림차순 정렬
Comparator sortByScoreDesc = Comparator.comparingInt(Player::getScore).reversed();
// 사용법:
// players.sort(sortByScoreDesc);
compare 메서드 계약 📜
모든 Comparator의 핵심은 compare(T o1, T o2) 메서드입니다. 이 메서드는 엄격한 계약을 따라야 합니다.
o1이o2보다 앞에 와야 하면 음의 정수를 반환합니다.o1이o2보다 뒤에 와야 하면 양의 정수를 반환합니다.o1과o2가 정렬 순서상 같다고 간주되면 0을 반환합니다.
Integer.compare(x, y)나 String.compareTo(s)와 같은 헬퍼 메서드들은 이미 이 계약을 따르므로 compare 메서드를 구현하는 데 매우 유용합니다.
요약하자면, Comparator는 유연하고 재사용 가능한 정렬 로직을 제공하는 자바 컬렉션 프레임워크의 필수 도구입니다. 특히 자바 8에서 도입된 유창하고 가독성 좋은 헬퍼 메서드 덕분에 간단하고 복잡한 정렬 규칙을 즉석에서 매우 쉽게 정의할 수 있게 되었습니다.
references